In your final repo, there should be an R markdown file that organizes all computational steps for evaluating your proposed Facial Expression Recognition framework.

This file is currently a template for running evaluation experiments. You should update it according to your codes but following precisely th e same structure.

if(!require("EBImage")){
  install.packages("BiocManager")
  BiocManager::install("EBImage")
}
if(!require("R.matlab")){
  install.packages("R.matlab")
}
if(!require("readxl")){
  install.packages("readxl")
}

if(!require("dplyr")){
  install.packages("dplyr")
}
if(!require("readxl")){
  install.packages("readxl")
}

if(!require("ggplot2")){
  install.packages("ggplot2")
}

if(!require("caret")){
  install.packages("caret")
}

if(!require("glmnet")){
  install.packages("glmnet")
}

if(!require("WeightedROC")){
  install.packages("WeightedROC")
}

library(R.matlab)
library(readxl)
library(dplyr)
library(EBImage)
library(ggplot2)
library(caret)
library(glmnet)
library(WeightedROC)

Step 0 set work directories

set.seed(2020)
setwd("./")
# here replace it with your own path or manually set it in RStudio to where this rmd file is located. 
# use relative path for reproducibility

Provide directories for training images. Training images and Training fiducial points will be in different subfolders.

train_dir <- "../../train_set/" # This will be modified for different data sets.
train_image_dir <- paste(train_dir, "images/", sep="")
train_pt_dir <- paste(train_dir,  "points/", sep="")
train_label_path <- paste(train_dir, "label.csv", sep="") 

Step 1: set up controls for evaluation experiments.

In this chunk, we have a set of controls for the evaluation experiments.

run.cv <- TRUE # run cross-validation on the training set
sample.reweight <- FALSE # run sample reweighting in model training
K <- 5  # number of CV folds
run.feature.train <- TRUE # process features for training set
run.test <- TRUE # run evaluation on an independent test set
run.feature.test <- TRUE # process features for test set

Using cross-validation or independent test set evaluation, we compare the performance of models with different specifications. In this Starter Code, we tune parameter lambda (the amount of shrinkage) for logistic regression with LASSO penalty.

lmbd = c(1e-3, 5e-3, 1e-2, 5e-2, 1e-1)
model_labels = paste("LASSO Penalty with lambda =", lmbd)

Step 2: import data and train-test split

#train-test split
info <- read.csv(train_label_path)
n <- nrow(info)
n_train <- round(n*(4/5), 0)
train_idx <- sample(info$Index, n_train, replace = F)
test_idx <- setdiff(info$Index, train_idx) #get the index in info$Index but different from train_idx

If you choose to extract features from images, such as using Gabor filter, R memory will exhaust all images are read together. The solution is to repeat reading a smaller batch(e.g 100) and process them.

n_files <- length(list.files(train_image_dir))

image_list <- list()
for(i in 1:100){
   image_list[[i]] <- readImage(paste0(train_image_dir, sprintf("%04d", i), ".jpg"))
}

Fiducial points are stored in matlab format. In this step, we read them and store them in a list.

#function to read fiducial points
#input: index
#output: matrix of fiducial points corresponding to the index
readMat.matrix <- function(index){
     return(round(readMat(paste0(train_pt_dir, sprintf("%04d", index), ".mat"))[[1]],0))
}

#load fiducial points
fiducial_pt_list <- lapply(1:n_files, readMat.matrix)
save(fiducial_pt_list, file="../output/fiducial_pt_list.RData")

Step 3: construct features and responses

Figure1

feature.R should be the wrapper for all your feature engineering functions and options. The function feature( ) should have options that correspond to different scenarios for your project and produces an R object that contains features and responses that are required by all the models you are going to evaluate later.

source("../lib/feature.R")
tm_feature_train <- NA
if(run.feature.train){
  tm_feature_train <- system.time(dat_train <- feature(fiducial_pt_list, train_idx))
  save(dat_train, file="../output/feature_train.RData")
}else{
  load(file="../output/feature_train.RData")
}

tm_feature_test <- NA
if(run.feature.test){
  tm_feature_test <- system.time(dat_test <- feature(fiducial_pt_list, test_idx))
  save(dat_test, file="../output/feature_test.RData")
}else{
  load(file="../output/feature_test.RData")
}

Step 4: Train a classification model with training features and responses

Call the train model and test model from library.

train.R and test.R should be wrappers for all your model training steps and your classification/prediction steps.

source("../lib/train.R") 
source("../lib/test.R")

Model selection with cross-validation

  • Do model selection by choosing among different values of training model parameters.
source("../lib/cross_validation.R")
feature_train = as.matrix(dat_train[, -6007])
label_train = as.integer(dat_train$label)
feature_train_new <- data.frame(read.csv("../"))
label_train_new <- data.frame(read.csv("../"))
feature_train_new <- as.matrix(feature_train_new[, -1])
label_train_new <- as.matrix(label_train_new[, -1])
if(run.cv){
  res_cv <- matrix(0, nrow = length(lmbd), ncol = 4)
  for(i in 1:length(lmbd)){
    cat("lambda = ", lmbd[i], "\n")
    res_cv[i,] <- cv.function(features = feature_train, labels = label_train, K, 
                              l = lmbd[i], reweight = sample.reweight)
  save(res_cv, file="../output/res_cv.RData")
  }
}else{
  load("../output/res_cv.RData")
}

Visualize cross-validation results.

  
res_cv <- as.data.frame(res_cv) 
colnames(res_cv) <- c("mean_error", "sd_error", "mean_AUC", "sd_AUC")
res_cv$k = as.factor(lmbd)

if(run.cv){
  p1 <- res_cv %>% 
    ggplot(aes(x = as.factor(lmbd), y = mean_error,
               ymin = mean_error - sd_error, ymax = mean_error + sd_error)) + 
    geom_crossbar() +
    theme(axis.text.x = element_text(angle = 90, hjust = 1))
  
  p2 <- res_cv %>% 
    ggplot(aes(x = as.factor(lmbd), y = mean_AUC,
               ymin = mean_AUC - sd_AUC, ymax = mean_AUC + sd_AUC)) + 
    geom_crossbar() +
    theme(axis.text.x = element_text(angle = 90, hjust = 1))
  
  print(p1)
  print(p2)
}
  • Choose the “best” parameter value
par_best <- lmbd[which.min(res_cv$mean_error)] # lmbd[which.max(res_cv$mean_AUC)]
  • Train the model with the entire training set using the selected model (model parameter) via cross-validation.
# training weights
weight_train <- rep(NA, length(label_train))
for (v in unique(label_train)){
  weight_train[label_train == v] = 0.5 * length(label_train) / length(label_train[label_train == v])
}
if (sample.reweight){
  tm_train <- system.time(fit_train <- train(feature_train, label_train, w = weight_train, par_best))
} else {
  tm_train <- system.time(fit_train <- train(feature_train, label_train, w = NULL, par_best))
}
save(fit_train, file="../output/fit_train.RData")

Step 5: Run test on test images

tm_test = NA
feature_test <- as.matrix(dat_test[, -6007])
if(run.test){
  load(file="../output/fit_train.RData")
  tm_test <- system.time({label_pred <- as.integer(test(fit_train, feature_test, pred.type = 'class')); 
                          prob_pred <- test(fit_train, feature_test, pred.type = 'response')})
}
## reweight the test data to represent a balanced label distribution
label_test <- as.integer(dat_test$label)
weight_test <- rep(NA, length(label_test))
for (v in unique(label_test)){
  weight_test[label_test == v] = 0.5 * length(label_test) / length(label_test[label_test == v])
}

accu <- sum(weight_test * (label_pred == label_test)) / sum(weight_test)
tpr.fpr <- WeightedROC(prob_pred, label_test, weight_test)
auc <- WeightedAUC(tpr.fpr)


cat("The accuracy of model:", model_labels[which.min(res_cv$mean_error)], "is", accu*100, "%.\n")
cat("The AUC of model:", model_labels[which.min(res_cv$mean_error)], "is", auc, ".\n")

Summarize Running Time

Prediction performance matters, so does the running times for constructing features and for training the model, especially when the computation resource is limited.

cat("Time for constructing training features=", tm_feature_train[1], "s \n")
cat("Time for constructing testing features=", tm_feature_test[1], "s \n")
cat("Time for training model=", tm_train[1], "s \n") 
cat("Time for testing model=", tm_test[1], "s \n")

5.Advanced model

5.1 Random Forest

Random forest is an ensemble machine learning model. The Random Forest model is a collection of several decision trees. These trees come together to a combined decision to give the output. The Random Forest model should have trees which are uncorrelated.

When data points are extremely complicated in arrangement, a simple classification model like Logistic Regression may underfit the model and make wrong predictions. Further, the time taken for the system to process it would also be considerably high. Therefore, we are losing out on both ends. In such scenario, Random Forest can realize that and have more accurate predictions. Further, overfitting is avoided and the model can also handle missing values.

5.1.1 cross-validation

After the cross validation process, we don’t see any remarkable difference between groups. It may due to the feature of the random forest which have trees are uncorrelated. So basically, we use the data after cross-validation to avoid any potential issues later.

library(AUC)
run.cv.rf<-FALSE
source("../lib/cross_validarion_rf.R")
source("../lib/rand_f.R")
feature_train = as.matrix(dat_train[, -6007])
label_train = as.integer(dat_train$label) 

ntrees<-c(100,200,300,400,500)
mtrys<-c(5,10)
if(run.cv.rf){
  res_cv_rf <- matrix(0, nrow = length(ntrees)*length(mtrys), ncol = 2)
  for(i in 1:length(mtrys)){
    cat("ntree = ", ntrees[i], "\n")
    for(j in 1:length(ntrees)){
    cat("mtry = ", mtrys[j], "\n")
    res_cv_rf[i,] <- cv.function_rf(
      features = feature_train, labels = label_train, K, ntree = ntrees[j],mtry=mtrys[i])
  }}
  save(res_cv_rf, file="../output/res_cv_RF.RData")
  
}else{
  #load("../output/res_cv_RF.RData")
}
5.1.2 Choosing Random Forest tunning parameter

By running the model with different parameter with same training data. We get several different number of accuracy and AUC. Since we do not need to consider overfitting for the random forest model and in the seek of time for the training model and test model, we decided to choose the parameter with the highest accuracy and AUC with the minimum number of trees and mtrys.

So we choose the parameter, ntrees = 100, mtry =20.

source("../lib/rand_f.R")
# training rf
# ntree=1000,  nodesize=25, samplesize=nrow(trainx)
run.train.RF<-FALSE
feature_train = as.matrix(dat_train[, -6007])
label_train = as.integer(dat_train$label) 
ntrees<-c(25,100,300,500,800)
mtrys<-c(20)
run.find.RF<-TRUE
if (run.find.RF) {
     res_pa_rf <- matrix(NA,nrow=5,ncol=2)
     for(i in 1:length(ntrees)){
         randomforest_fit <- train_RF(feature_train, label_train, ntree = ntrees[i], mtry = mtrys)
         label_pred <- as.integer(test_RF(randomforest_fit, feature_test))
         label_pred <- ifelse(label_pred == 2, 0, 1)
         accu_rf = sum(label_pred==dat_test$label)/length(dat_test$label)
         res_pa_rf[i,1]<-accu_rf
         tpr.fpr <- WeightedROC(label_pred, dat_test$label)
         auc_rf <- WeightedAUC(tpr.fpr)
         res_pa_rf[i,2]<-auc_rf
         }
      save(res_pa_rf, file="../output/res_pa_RF.RData")
}else{
   load("../output/res_pa_RF.RData")
}
     

visualization of the parameter to choose

load("../output/res_pa_RF.RData")
plot(ntrees, res_pa_rf[,1], type = "l",ylim=c(0.76,0.795))
points(ntrees,res_pa_rf[,2]+0.25)

5.1.3 Train the model with tunning parameter on original dataset

Train the model, save the data to the output rDATA with the parameter find before.

source("../lib/rand_f.R")
# training rf

run.train.RF<-TRUE
feature_train = as.matrix(dat_train[, -6007])
label_train = as.integer(dat_train$label) 

if (run.train.RF) {
  tm_rfTrain <- system.time(
     randomforest_fit <- train_RF(feature_train, label_train, ntree = 100, mtry = 20)
)
save(randomforest_fit, file = "../output/train_randomforest.RData")
}
5.1.4 Test the model by train data on original dataset
# testing rf
tm_rf_train_test = NA

run.test.RF<-TRUE
if(run.test.RF){
  load(file="../output/train_randomforest.RData")
  tm_rf_train_test <- system.time(
    label_pred_train <- as.integer(test_RF(randomforest_fit, feature_train))); 
}

label_pred_train <- ifelse(label_pred_train == 2, 1, 2)
accu_rf_train_test <- sum(label_pred_train==label_train)/length(label_train)

tpr.fpr_train <- WeightedROC(label_pred_train, label_train)
auc_rf_train <- WeightedAUC(tpr.fpr_train)
5.1.5 Test the model by test data on original dataset

Calculate testing time and accuracy on testing data

# testing rf
tm_test_RF = NA
feature_test <- as.matrix(dat_test[, -6007])
run.test.RF<-TRUE
if(run.test.RF){
  load(file="../output/train_randomforest.RData")
  tm_test_RF <- system.time(
    label_pred <- as.integer(test_RF(randomforest_fit, feature_test))); 
}
5.1.6 Train the model with tunning parameter on resampled dataset

Train the model, save the data to the output rDATA with the parameter find before.

source("../lib/rand_f.R")
# training rf
tm_rfTrain_resample<-NA
run.train.RF<-TRUE

if (run.train.RF) {
  tm_rfTrain_resample <- system.time(
     randomforest_fit_resample <- train_RF(feature_train_new, label_train_new, ntree = 100, mtry = 20)
)
save(randomforest_fit_resample, file = "../output/train_randomforest_resample.RData")
}
5.1.7 Test the model by train data on resampled dataset
# testing rf
tm_rf_train_test_resample = NA

run.test.RF<-TRUE
if(run.test.RF){
  load(file="../output/train_randomforest_resample.RData")
  tm_rf_train_test_resample <- system.time(
    label_pred_train_resample <- as.integer(test_RF(randomforest_fit_resample, feature_train_new))); 
}

label_pred_train_resample <- ifelse(label_pred_train_resample == 2, 1, 2)
accu_rf_train_test_resample <- sum(label_pred_train_resample==label_train_new)/length(label_train_new)

tpr.fpr_train_resample <- WeightedROC(label_pred_train_resample, label_train_new)
auc_rf_train_resample <- WeightedAUC(tpr.fpr_train_resample)
5.1.8 Test the resampled model by test data on test dataset

Calculate testing time and accuracy on testing data

# testing rf
tm_test_RF_resample = NA
feature_test <- as.matrix(dat_test[, -6007])
run.test.RF<-TRUE
if(run.test.RF){
  load(file="../output/train_randomforest.RData")
  tm_test_RF_resample <- system.time(
    label_pred_resample <- as.integer(test_RF(randomforest_fit_resample, feature_test))); 
}
5.1.9 Summary of the random forest model
cat("The unweighted accuracy of the random forest model on train data is ", accu_rf_train_test*100, "%.\n")
The unweighted accuracy of the random forest model on train data is  91.58333 %.
cat("The unweighted AUC of the random forest model on train data is ", auc_rf_train, ".\n")
The unweighted AUC of the random forest model on train data is  0.7832618 .
cat("The unweighted accuracy of the random forest model on test data is ", accu_rf_test_test*100, "%.\n")
The unweighted accuracy of the random forest model on test data is  78.66667 %.
cat("The unweighted AUC of the random forest model on test data is ", auc_rf_test, ".\n")
The unweighted AUC of the random forest model on test data is  0.5205905 .
cat("The resampled accuracy of the random forest model on train data is ", accu_rf_train_test_resample*100, "%.\n")
The resampled accuracy of the random forest model on train data is  98.88803 %.
cat("The resampled AUC of the random forest model on train data is ", auc_rf_train_resample, ".\n")
The resampled AUC of the random forest model on train data is  0.9888831 .
cat("The resampled accuracy of the random forest model on test data is ", accu_rf_test_test_resample*100, "%.\n")
The resampled accuracy of the random forest model on test data is  94.66667 %.
cat("The resampled AUC of the random forest model on test data is ", auc_rf_test_resample, ".\n")
The resampled AUC of the random forest model on test data is  0.8787879 .
cat("Time for training model Random Forest = ", tm_rfTrain[1], "s \n")
Time for training model Random Forest =  118.398 s 
cat("Time for test model Random Forest on train data is ",tm_rf_train_test[1] , "s \n")
Time for test model Random Forest on train data is  0.746 s 
cat("Time for test model Random Forest on test data is ",tm_test_RF[1] , "s \n")
Time for test model Random Forest on test data is  0.176 s 

#####The test day code

# testing rf
tm_test_RF_td = NA
feature_test_td <- as.matrix(td[, -6007])
run.test.RF<-TRUE
if(run.test.RF){
  load(file="../output/train_randomforest.RData")
  tm_test_RF_td <- system.time(
    label_pred_td <- as.integer(test_RF(randomforest_fit_resample, feature_test_td)))
   write.csv(label_pred_td, "mydata.csv")
}

###Reference - Du, S., Tao, Y., & Martinez, A. M. (2014). Compound facial expressions of emotion. Proceedings of the National Academy of Sciences, 111(15), E1454-E1462.

LS0tCnRpdGxlOiAiTWFpbiIKYXV0aG9yOiAiQ2hlbmdsaWFuZyBUYW5nLCBZdWppZSBXYW5nLCBEaWFuZSBMdSwgVGlhbiBaaGVuZyIKb3V0cHV0OgogIHBkZl9kb2N1bWVudDogZGVmYXVsdAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCgpJbiB5b3VyIGZpbmFsIHJlcG8sIHRoZXJlIHNob3VsZCBiZSBhbiBSIG1hcmtkb3duIGZpbGUgdGhhdCBvcmdhbml6ZXMgKiphbGwgY29tcHV0YXRpb25hbCBzdGVwcyoqIGZvciBldmFsdWF0aW5nIHlvdXIgcHJvcG9zZWQgRmFjaWFsIEV4cHJlc3Npb24gUmVjb2duaXRpb24gZnJhbWV3b3JrLiAKClRoaXMgZmlsZSBpcyBjdXJyZW50bHkgYSB0ZW1wbGF0ZSBmb3IgcnVubmluZyBldmFsdWF0aW9uIGV4cGVyaW1lbnRzLiBZb3Ugc2hvdWxkIHVwZGF0ZSBpdCBhY2NvcmRpbmcgdG8geW91ciBjb2RlcyBidXQgZm9sbG93aW5nIHByZWNpc2VseSB0aCAgICBlIHNhbWUgc3RydWN0dXJlLiAKCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmlmKCFyZXF1aXJlKCJFQkltYWdlIikpewogIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikKICBCaW9jTWFuYWdlcjo6aW5zdGFsbCgiRUJJbWFnZSIpCn0KaWYoIXJlcXVpcmUoIlIubWF0bGFiIikpewogIGluc3RhbGwucGFja2FnZXMoIlIubWF0bGFiIikKfQppZighcmVxdWlyZSgicmVhZHhsIikpewogIGluc3RhbGwucGFja2FnZXMoInJlYWR4bCIpCn0KCmlmKCFyZXF1aXJlKCJkcGx5ciIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpCn0KaWYoIXJlcXVpcmUoInJlYWR4bCIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJyZWFkeGwiKQp9CgppZighcmVxdWlyZSgiZ2dwbG90MiIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikKfQoKaWYoIXJlcXVpcmUoImNhcmV0IikpewogIGluc3RhbGwucGFja2FnZXMoImNhcmV0IikKfQoKaWYoIXJlcXVpcmUoImdsbW5ldCIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJnbG1uZXQiKQp9CgppZighcmVxdWlyZSgiV2VpZ2h0ZWRST0MiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiV2VpZ2h0ZWRST0MiKQp9CgpsaWJyYXJ5KFIubWF0bGFiKQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeShkcGx5cikKbGlicmFyeShFQkltYWdlKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkoZ2xtbmV0KQpsaWJyYXJ5KFdlaWdodGVkUk9DKQpgYGAKCiMjIyBTdGVwIDAgc2V0IHdvcmsgZGlyZWN0b3JpZXMKYGBge3Igd2tkaXIsIGV2YWw9RkFMU0V9CnNldC5zZWVkKDIwMjApCnNldHdkKCIuLyIpCiMgaGVyZSByZXBsYWNlIGl0IHdpdGggeW91ciBvd24gcGF0aCBvciBtYW51YWxseSBzZXQgaXQgaW4gUlN0dWRpbyB0byB3aGVyZSB0aGlzIHJtZCBmaWxlIGlzIGxvY2F0ZWQuIAojIHVzZSByZWxhdGl2ZSBwYXRoIGZvciByZXByb2R1Y2liaWxpdHkKYGBgCgpQcm92aWRlIGRpcmVjdG9yaWVzIGZvciB0cmFpbmluZyBpbWFnZXMuIFRyYWluaW5nIGltYWdlcyBhbmQgVHJhaW5pbmcgZmlkdWNpYWwgcG9pbnRzIHdpbGwgYmUgaW4gZGlmZmVyZW50IHN1YmZvbGRlcnMuIApgYGB7cn0KdHJhaW5fZGlyIDwtICIuLi8uLi90cmFpbl9zZXQvIiAjIFRoaXMgd2lsbCBiZSBtb2RpZmllZCBmb3IgZGlmZmVyZW50IGRhdGEgc2V0cy4KdHJhaW5faW1hZ2VfZGlyIDwtIHBhc3RlKHRyYWluX2RpciwgImltYWdlcy8iLCBzZXA9IiIpCnRyYWluX3B0X2RpciA8LSBwYXN0ZSh0cmFpbl9kaXIsICAicG9pbnRzLyIsIHNlcD0iIikKdHJhaW5fbGFiZWxfcGF0aCA8LSBwYXN0ZSh0cmFpbl9kaXIsICJsYWJlbC5jc3YiLCBzZXA9IiIpIApgYGAKCiMjIyBTdGVwIDE6IHNldCB1cCBjb250cm9scyBmb3IgZXZhbHVhdGlvbiBleHBlcmltZW50cy4KCkluIHRoaXMgY2h1bmssIHdlIGhhdmUgYSBzZXQgb2YgY29udHJvbHMgZm9yIHRoZSBldmFsdWF0aW9uIGV4cGVyaW1lbnRzLiAKCisgKFQvRikgY3Jvc3MtdmFsaWRhdGlvbiBvbiB0aGUgdHJhaW5pbmcgc2V0CisgKFQvRikgcmV3ZWlnaHRpbmcgdGhlIHNhbXBsZXMgZm9yIHRyYWluaW5nIHNldCAKKyAobnVtYmVyKSBLLCB0aGUgbnVtYmVyIG9mIENWIGZvbGRzCisgKFQvRikgcHJvY2VzcyBmZWF0dXJlcyBmb3IgdHJhaW5pbmcgc2V0CisgKFQvRikgcnVuIGV2YWx1YXRpb24gb24gYW4gaW5kZXBlbmRlbnQgdGVzdCBzZXQKKyAoVC9GKSBwcm9jZXNzIGZlYXR1cmVzIGZvciB0ZXN0IHNldAoKYGBge3IgZXhwX3NldHVwfQpydW4uY3YgPC0gVFJVRSAjIHJ1biBjcm9zcy12YWxpZGF0aW9uIG9uIHRoZSB0cmFpbmluZyBzZXQKc2FtcGxlLnJld2VpZ2h0IDwtIEZBTFNFICMgcnVuIHNhbXBsZSByZXdlaWdodGluZyBpbiBtb2RlbCB0cmFpbmluZwpLIDwtIDUgICMgbnVtYmVyIG9mIENWIGZvbGRzCnJ1bi5mZWF0dXJlLnRyYWluIDwtIFRSVUUgIyBwcm9jZXNzIGZlYXR1cmVzIGZvciB0cmFpbmluZyBzZXQKcnVuLnRlc3QgPC0gVFJVRSAjIHJ1biBldmFsdWF0aW9uIG9uIGFuIGluZGVwZW5kZW50IHRlc3Qgc2V0CnJ1bi5mZWF0dXJlLnRlc3QgPC0gVFJVRSAjIHByb2Nlc3MgZmVhdHVyZXMgZm9yIHRlc3Qgc2V0CmBgYAoKVXNpbmcgY3Jvc3MtdmFsaWRhdGlvbiBvciBpbmRlcGVuZGVudCB0ZXN0IHNldCBldmFsdWF0aW9uLCB3ZSBjb21wYXJlIHRoZSBwZXJmb3JtYW5jZSBvZiBtb2RlbHMgd2l0aCBkaWZmZXJlbnQgc3BlY2lmaWNhdGlvbnMuIEluIHRoaXMgU3RhcnRlciBDb2RlLCB3ZSB0dW5lIHBhcmFtZXRlciBsYW1iZGEgKHRoZSBhbW91bnQgb2Ygc2hyaW5rYWdlKSBmb3IgbG9naXN0aWMgcmVncmVzc2lvbiB3aXRoIExBU1NPIHBlbmFsdHkuCgpgYGB7ciBtb2RlbF9zZXR1cH0KbG1iZCA9IGMoMWUtMywgNWUtMywgMWUtMiwgNWUtMiwgMWUtMSkKbW9kZWxfbGFiZWxzID0gcGFzdGUoIkxBU1NPIFBlbmFsdHkgd2l0aCBsYW1iZGEgPSIsIGxtYmQpCmBgYAoKIyMjIFN0ZXAgMjogaW1wb3J0IGRhdGEgYW5kIHRyYWluLXRlc3Qgc3BsaXQgCmBgYHtyfQojdHJhaW4tdGVzdCBzcGxpdAppbmZvIDwtIHJlYWQuY3N2KHRyYWluX2xhYmVsX3BhdGgpCm4gPC0gbnJvdyhpbmZvKQpuX3RyYWluIDwtIHJvdW5kKG4qKDQvNSksIDApCnRyYWluX2lkeCA8LSBzYW1wbGUoaW5mbyRJbmRleCwgbl90cmFpbiwgcmVwbGFjZSA9IEYpCnRlc3RfaWR4IDwtIHNldGRpZmYoaW5mbyRJbmRleCwgdHJhaW5faWR4KSAjZ2V0IHRoZSBpbmRleCBpbiBpbmZvJEluZGV4IGJ1dCBkaWZmZXJlbnQgZnJvbSB0cmFpbl9pZHgKYGBgCgpJZiB5b3UgY2hvb3NlIHRvIGV4dHJhY3QgZmVhdHVyZXMgZnJvbSBpbWFnZXMsIHN1Y2ggYXMgdXNpbmcgR2Fib3IgZmlsdGVyLCBSIG1lbW9yeSB3aWxsIGV4aGF1c3QgYWxsIGltYWdlcyBhcmUgcmVhZCB0b2dldGhlci4gVGhlIHNvbHV0aW9uIGlzIHRvIHJlcGVhdCByZWFkaW5nIGEgc21hbGxlciBiYXRjaChlLmcgMTAwKSBhbmQgcHJvY2VzcyB0aGVtLiAKYGBge3J9Cm5fZmlsZXMgPC0gbGVuZ3RoKGxpc3QuZmlsZXModHJhaW5faW1hZ2VfZGlyKSkKCmltYWdlX2xpc3QgPC0gbGlzdCgpCmZvcihpIGluIDE6MTAwKXsKICAgaW1hZ2VfbGlzdFtbaV1dIDwtIHJlYWRJbWFnZShwYXN0ZTAodHJhaW5faW1hZ2VfZGlyLCBzcHJpbnRmKCIlMDRkIiwgaSksICIuanBnIikpCn0KYGBgCgpGaWR1Y2lhbCBwb2ludHMgYXJlIHN0b3JlZCBpbiBtYXRsYWIgZm9ybWF0LiBJbiB0aGlzIHN0ZXAsIHdlIHJlYWQgdGhlbSBhbmQgc3RvcmUgdGhlbSBpbiBhIGxpc3QuCmBgYHtyIHJlYWQgZmlkdWNpYWwgcG9pbnRzfQojZnVuY3Rpb24gdG8gcmVhZCBmaWR1Y2lhbCBwb2ludHMKI2lucHV0OiBpbmRleAojb3V0cHV0OiBtYXRyaXggb2YgZmlkdWNpYWwgcG9pbnRzIGNvcnJlc3BvbmRpbmcgdG8gdGhlIGluZGV4CnJlYWRNYXQubWF0cml4IDwtIGZ1bmN0aW9uKGluZGV4KXsKICAgICByZXR1cm4ocm91bmQocmVhZE1hdChwYXN0ZTAodHJhaW5fcHRfZGlyLCBzcHJpbnRmKCIlMDRkIiwgaW5kZXgpLCAiLm1hdCIpKVtbMV1dLDApKQp9CgojbG9hZCBmaWR1Y2lhbCBwb2ludHMKZmlkdWNpYWxfcHRfbGlzdCA8LSBsYXBwbHkoMTpuX2ZpbGVzLCByZWFkTWF0Lm1hdHJpeCkKc2F2ZShmaWR1Y2lhbF9wdF9saXN0LCBmaWxlPSIuLi9vdXRwdXQvZmlkdWNpYWxfcHRfbGlzdC5SRGF0YSIpCmBgYAoKIyMjIFN0ZXAgMzogY29uc3RydWN0IGZlYXR1cmVzIGFuZCByZXNwb25zZXMKCisgVGhlIGZvbGxvdyBwbG90cyBzaG93IGhvdyBwYWlyd2lzZSBkaXN0YW5jZSBiZXR3ZWVuIGZpZHVjaWFsIHBvaW50cyBjYW4gd29yayBhcyBmZWF0dXJlIGZvciBmYWNpYWwgZW1vdGlvbiByZWNvZ25pdGlvbi4KCiAgKyBJbiB0aGUgZmlyc3QgY29sdW1uLCA3OCBmaWR1Y2lhbHMgcG9pbnRzIG9mIGVhY2ggZW1vdGlvbiBhcmUgbWFya2VkIGluIG9yZGVyLiAKICArIEluIHRoZSBzZWNvbmQgY29sdW1uIGRpc3RyaWJ1dGlvbnMgb2YgdmVydGljYWwgZGlzdGFuY2UgYmV0d2VlbiByaWdodCBwdXBpbCgxKSBhbmQgIHJpZ2h0IGJyb3cgcGVhaygyMSkgYXJlIHNob3duIGluICBoaXN0b2dyYW1zLiBGb3IgZXhhbXBsZSwgdGhlIGRpc3RhbmNlIG9mIGFuIGFuZ3J5IGZhY2UgdGVuZHMgdG8gYmUgc2hvcnRlciB0aGFuIHRoYXQgb2YgYSBzdXJwcmlzZWQgZmFjZS4KICArIFRoZSB0aGlyZCBjb2x1bW4gaXMgdGhlIGRpc3RyaWJ1dGlvbnMgb2YgdmVydGljYWwgZGlzdGFuY2VzIGJldHdlZW4gcmlnaHQgbW91dGggY29ybmVyKDUwKQphbmQgdGhlIG1pZHBvaW50IG9mIHRoZSB1cHBlciBsaXAoNTIpLiAgRm9yIGV4YW1wbGUsIHRoZSBkaXN0YW5jZSBvZiBhbiBoYXBweSBmYWNlIHRlbmRzIHRvIGJlIHNob3J0ZXIgdGhhbiB0aGF0IG9mIGEgc2FkIGZhY2UuCgohW0ZpZ3VyZTFdKC4uL2ZpZ3MvZmVhdHVyZV92aXN1YWxpemF0aW9uLmpwZykKCmBmZWF0dXJlLlJgIHNob3VsZCBiZSB0aGUgd3JhcHBlciBmb3IgYWxsIHlvdXIgZmVhdHVyZSBlbmdpbmVlcmluZyBmdW5jdGlvbnMgYW5kIG9wdGlvbnMuIFRoZSBmdW5jdGlvbiBgZmVhdHVyZSggKWAgc2hvdWxkIGhhdmUgb3B0aW9ucyB0aGF0IGNvcnJlc3BvbmQgdG8gZGlmZmVyZW50IHNjZW5hcmlvcyBmb3IgeW91ciBwcm9qZWN0IGFuZCBwcm9kdWNlcyBhbiBSIG9iamVjdCB0aGF0IGNvbnRhaW5zIGZlYXR1cmVzIGFuZCByZXNwb25zZXMgdGhhdCBhcmUgcmVxdWlyZWQgYnkgYWxsIHRoZSBtb2RlbHMgeW91IGFyZSBnb2luZyB0byBldmFsdWF0ZSBsYXRlci4gCiAgCiAgKyBgZmVhdHVyZS5SYAogICsgSW5wdXQ6IGxpc3Qgb2YgaW1hZ2VzIG9yIGZpZHVjaWFsIHBvaW50CiAgKyBPdXRwdXQ6IGFuIFJEYXRhIGZpbGUgdGhhdCBjb250YWlucyBleHRyYWN0ZWQgZmVhdHVyZXMgYW5kIGNvcnJlc3BvbmRpbmcgcmVzcG9uc2VzCgpgYGB7ciBmZWF0dXJlfQpzb3VyY2UoIi4uL2xpYi9mZWF0dXJlLlIiKQp0bV9mZWF0dXJlX3RyYWluIDwtIE5BCmlmKHJ1bi5mZWF0dXJlLnRyYWluKXsKICB0bV9mZWF0dXJlX3RyYWluIDwtIHN5c3RlbS50aW1lKGRhdF90cmFpbiA8LSBmZWF0dXJlKGZpZHVjaWFsX3B0X2xpc3QsIHRyYWluX2lkeCkpCiAgc2F2ZShkYXRfdHJhaW4sIGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3RyYWluLlJEYXRhIikKfWVsc2V7CiAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90cmFpbi5SRGF0YSIpCn0KCnRtX2ZlYXR1cmVfdGVzdCA8LSBOQQppZihydW4uZmVhdHVyZS50ZXN0KXsKICB0bV9mZWF0dXJlX3Rlc3QgPC0gc3lzdGVtLnRpbWUoZGF0X3Rlc3QgPC0gZmVhdHVyZShmaWR1Y2lhbF9wdF9saXN0LCB0ZXN0X2lkeCkpCiAgc2F2ZShkYXRfdGVzdCwgZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfdGVzdC5SRGF0YSIpCn1lbHNlewogIGxvYWQoZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfdGVzdC5SRGF0YSIpCn0KCgpgYGAKCiMjIyBTdGVwIDQ6IFRyYWluIGEgY2xhc3NpZmljYXRpb24gbW9kZWwgd2l0aCB0cmFpbmluZyBmZWF0dXJlcyBhbmQgcmVzcG9uc2VzCkNhbGwgdGhlIHRyYWluIG1vZGVsIGFuZCB0ZXN0IG1vZGVsIGZyb20gbGlicmFyeS4gCgpgdHJhaW4uUmAgYW5kIGB0ZXN0LlJgIHNob3VsZCBiZSB3cmFwcGVycyBmb3IgYWxsIHlvdXIgbW9kZWwgdHJhaW5pbmcgc3RlcHMgYW5kIHlvdXIgY2xhc3NpZmljYXRpb24vcHJlZGljdGlvbiBzdGVwcy4gCgorIGB0cmFpbi5SYAogICsgSW5wdXQ6IGEgZGF0YSBmcmFtZSBjb250YWluaW5nIGZlYXR1cmVzIGFuZCBsYWJlbHMgYW5kIGEgcGFyYW1ldGVyIGxpc3QuCiAgKyBPdXRwdXQ6YSB0cmFpbmVkIG1vZGVsCisgYHRlc3QuUmAKICArIElucHV0OiB0aGUgZml0dGVkIGNsYXNzaWZpY2F0aW9uIG1vZGVsIHVzaW5nIHRyYWluaW5nIGRhdGEgYW5kIHByb2Nlc3NlZCBmZWF0dXJlcyBmcm9tIHRlc3RpbmcgaW1hZ2VzIAogICsgSW5wdXQ6IGFuIFIgb2JqZWN0IHRoYXQgY29udGFpbnMgYSB0cmFpbmVkIGNsYXNzaWZpZXIuCiAgKyBPdXRwdXQ6IHRyYWluaW5nIG1vZGVsIHNwZWNpZmljYXRpb24KCisgSW4gdGhpcyBTdGFydGVyIENvZGUsIHdlIHVzZSBsb2dpc3RpYyByZWdyZXNzaW9uIHdpdGggTEFTU08gcGVuYWx0eSB0byBkbyBjbGFzc2lmaWNhdGlvbi4gCgpgYGB7ciBsb2FkbGlifQpzb3VyY2UoIi4uL2xpYi90cmFpbi5SIikgCnNvdXJjZSgiLi4vbGliL3Rlc3QuUiIpCmBgYAoKIyMjIyBNb2RlbCBzZWxlY3Rpb24gd2l0aCBjcm9zcy12YWxpZGF0aW9uCiogRG8gbW9kZWwgc2VsZWN0aW9uIGJ5IGNob29zaW5nIGFtb25nIGRpZmZlcmVudCB2YWx1ZXMgb2YgdHJhaW5pbmcgbW9kZWwgcGFyYW1ldGVycy4KCmBgYHtyIHJ1bmN2fQpzb3VyY2UoIi4uL2xpYi9jcm9zc192YWxpZGF0aW9uLlIiKQpmZWF0dXJlX3RyYWluID0gYXMubWF0cml4KGRhdF90cmFpblssIC02MDA3XSkKbGFiZWxfdHJhaW4gPSBhcy5pbnRlZ2VyKGRhdF90cmFpbiRsYWJlbCkKZmVhdHVyZV90cmFpbl9uZXcgPC0gZGF0YS5mcmFtZShyZWFkLmNzdigiLi4vIikpCmxhYmVsX3RyYWluX25ldyA8LSBkYXRhLmZyYW1lKHJlYWQuY3N2KCIuLi8iKSkKZmVhdHVyZV90cmFpbl9uZXcgPC0gYXMubWF0cml4KGZlYXR1cmVfdHJhaW5fbmV3WywgLTFdKQpsYWJlbF90cmFpbl9uZXcgPC0gYXMubWF0cml4KGxhYmVsX3RyYWluX25ld1ssIC0xXSkKCmBgYAoKYGBge3IgbGFzc28gfQppZihydW4uY3YpewogIHJlc19jdiA8LSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChsbWJkKSwgbmNvbCA9IDQpCiAgZm9yKGkgaW4gMTpsZW5ndGgobG1iZCkpewogICAgY2F0KCJsYW1iZGEgPSAiLCBsbWJkW2ldLCAiXG4iKQogICAgcmVzX2N2W2ksXSA8LSBjdi5mdW5jdGlvbihmZWF0dXJlcyA9IGZlYXR1cmVfdHJhaW4sIGxhYmVscyA9IGxhYmVsX3RyYWluLCBLLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbCA9IGxtYmRbaV0sIHJld2VpZ2h0ID0gc2FtcGxlLnJld2VpZ2h0KQogIHNhdmUocmVzX2N2LCBmaWxlPSIuLi9vdXRwdXQvcmVzX2N2LlJEYXRhIikKICB9Cn1lbHNlewogIGxvYWQoIi4uL291dHB1dC9yZXNfY3YuUkRhdGEiKQp9CmBgYAoKVmlzdWFsaXplIGNyb3NzLXZhbGlkYXRpb24gcmVzdWx0cy4gCmBgYHtyIGN2X3Zpc30KICAKcmVzX2N2IDwtIGFzLmRhdGEuZnJhbWUocmVzX2N2KSAKY29sbmFtZXMocmVzX2N2KSA8LSBjKCJtZWFuX2Vycm9yIiwgInNkX2Vycm9yIiwgIm1lYW5fQVVDIiwgInNkX0FVQyIpCnJlc19jdiRrID0gYXMuZmFjdG9yKGxtYmQpCgppZihydW4uY3YpewogIHAxIDwtIHJlc19jdiAlPiUgCiAgICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IobG1iZCksIHkgPSBtZWFuX2Vycm9yLAogICAgICAgICAgICAgICB5bWluID0gbWVhbl9lcnJvciAtIHNkX2Vycm9yLCB5bWF4ID0gbWVhbl9lcnJvciArIHNkX2Vycm9yKSkgKyAKICAgIGdlb21fY3Jvc3NiYXIoKSArCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQogIAogIHAyIDwtIHJlc19jdiAlPiUgCiAgICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IobG1iZCksIHkgPSBtZWFuX0FVQywKICAgICAgICAgICAgICAgeW1pbiA9IG1lYW5fQVVDIC0gc2RfQVVDLCB5bWF4ID0gbWVhbl9BVUMgKyBzZF9BVUMpKSArIAogICAgZ2VvbV9jcm9zc2JhcigpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpCiAgCiAgcHJpbnQocDEpCiAgcHJpbnQocDIpCn0KCgpgYGAKCgoqIENob29zZSB0aGUgImJlc3QiIHBhcmFtZXRlciB2YWx1ZQpgYGB7ciBiZXN0X21vZGVsfQpwYXJfYmVzdCA8LSBsbWJkW3doaWNoLm1pbihyZXNfY3YkbWVhbl9lcnJvcildICMgbG1iZFt3aGljaC5tYXgocmVzX2N2JG1lYW5fQVVDKV0KYGBgCgoqIFRyYWluIHRoZSBtb2RlbCB3aXRoIHRoZSBlbnRpcmUgdHJhaW5pbmcgc2V0IHVzaW5nIHRoZSBzZWxlY3RlZCBtb2RlbCAobW9kZWwgcGFyYW1ldGVyKSB2aWEgY3Jvc3MtdmFsaWRhdGlvbi4KYGBge3IgZmluYWxfdHJhaW59CiMgdHJhaW5pbmcgd2VpZ2h0cwp3ZWlnaHRfdHJhaW4gPC0gcmVwKE5BLCBsZW5ndGgobGFiZWxfdHJhaW4pKQpmb3IgKHYgaW4gdW5pcXVlKGxhYmVsX3RyYWluKSl7CiAgd2VpZ2h0X3RyYWluW2xhYmVsX3RyYWluID09IHZdID0gMC41ICogbGVuZ3RoKGxhYmVsX3RyYWluKSAvIGxlbmd0aChsYWJlbF90cmFpbltsYWJlbF90cmFpbiA9PSB2XSkKfQppZiAoc2FtcGxlLnJld2VpZ2h0KXsKICB0bV90cmFpbiA8LSBzeXN0ZW0udGltZShmaXRfdHJhaW4gPC0gdHJhaW4oZmVhdHVyZV90cmFpbiwgbGFiZWxfdHJhaW4sIHcgPSB3ZWlnaHRfdHJhaW4sIHBhcl9iZXN0KSkKfSBlbHNlIHsKICB0bV90cmFpbiA8LSBzeXN0ZW0udGltZShmaXRfdHJhaW4gPC0gdHJhaW4oZmVhdHVyZV90cmFpbiwgbGFiZWxfdHJhaW4sIHcgPSBOVUxMLCBwYXJfYmVzdCkpCn0Kc2F2ZShmaXRfdHJhaW4sIGZpbGU9Ii4uL291dHB1dC9maXRfdHJhaW4uUkRhdGEiKQpgYGAKCiMjIyBTdGVwIDU6IFJ1biB0ZXN0IG9uIHRlc3QgaW1hZ2VzCmBgYHtyIHRlc3R9CnRtX3Rlc3QgPSBOQQpmZWF0dXJlX3Rlc3QgPC0gYXMubWF0cml4KGRhdF90ZXN0WywgLTYwMDddKQppZihydW4udGVzdCl7CiAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluLlJEYXRhIikKICB0bV90ZXN0IDwtIHN5c3RlbS50aW1lKHtsYWJlbF9wcmVkIDwtIGFzLmludGVnZXIodGVzdChmaXRfdHJhaW4sIGZlYXR1cmVfdGVzdCwgcHJlZC50eXBlID0gJ2NsYXNzJykpOyAKICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9iX3ByZWQgPC0gdGVzdChmaXRfdHJhaW4sIGZlYXR1cmVfdGVzdCwgcHJlZC50eXBlID0gJ3Jlc3BvbnNlJyl9KQp9CmBgYAoKCiogZXZhbHVhdGlvbgpgYGB7cn0KIyMgcmV3ZWlnaHQgdGhlIHRlc3QgZGF0YSB0byByZXByZXNlbnQgYSBiYWxhbmNlZCBsYWJlbCBkaXN0cmlidXRpb24KbGFiZWxfdGVzdCA8LSBhcy5pbnRlZ2VyKGRhdF90ZXN0JGxhYmVsKQp3ZWlnaHRfdGVzdCA8LSByZXAoTkEsIGxlbmd0aChsYWJlbF90ZXN0KSkKZm9yICh2IGluIHVuaXF1ZShsYWJlbF90ZXN0KSl7CiAgd2VpZ2h0X3Rlc3RbbGFiZWxfdGVzdCA9PSB2XSA9IDAuNSAqIGxlbmd0aChsYWJlbF90ZXN0KSAvIGxlbmd0aChsYWJlbF90ZXN0W2xhYmVsX3Rlc3QgPT0gdl0pCn0KCmFjY3UgPC0gc3VtKHdlaWdodF90ZXN0ICogKGxhYmVsX3ByZWQgPT0gbGFiZWxfdGVzdCkpIC8gc3VtKHdlaWdodF90ZXN0KQp0cHIuZnByIDwtIFdlaWdodGVkUk9DKHByb2JfcHJlZCwgbGFiZWxfdGVzdCwgd2VpZ2h0X3Rlc3QpCmF1YyA8LSBXZWlnaHRlZEFVQyh0cHIuZnByKQoKCmNhdCgiVGhlIGFjY3VyYWN5IG9mIG1vZGVsOiIsIG1vZGVsX2xhYmVsc1t3aGljaC5taW4ocmVzX2N2JG1lYW5fZXJyb3IpXSwgImlzIiwgYWNjdSoxMDAsICIlLlxuIikKY2F0KCJUaGUgQVVDIG9mIG1vZGVsOiIsIG1vZGVsX2xhYmVsc1t3aGljaC5taW4ocmVzX2N2JG1lYW5fZXJyb3IpXSwgImlzIiwgYXVjLCAiLlxuIikKCgpgYGAKCiMjIyBTdW1tYXJpemUgUnVubmluZyBUaW1lClByZWRpY3Rpb24gcGVyZm9ybWFuY2UgbWF0dGVycywgc28gZG9lcyB0aGUgcnVubmluZyB0aW1lcyBmb3IgY29uc3RydWN0aW5nIGZlYXR1cmVzIGFuZCBmb3IgdHJhaW5pbmcgdGhlIG1vZGVsLCBlc3BlY2lhbGx5IHdoZW4gdGhlIGNvbXB1dGF0aW9uIHJlc291cmNlIGlzIGxpbWl0ZWQuIApgYGB7ciBydW5uaW5nX3RpbWV9CmNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIHRyYWluaW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdHJhaW5bMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdGVzdGluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3Rlc3RbMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciB0cmFpbmluZyBtb2RlbD0iLCB0bV90cmFpblsxXSwgInMgXG4iKSAKY2F0KCJUaW1lIGZvciB0ZXN0aW5nIG1vZGVsPSIsIHRtX3Rlc3RbMV0sICJzIFxuIikKYGBgCgoKCiMjIyMgNS5BZHZhbmNlZCBtb2RlbAojIyMjIDUuMSBSYW5kb20gRm9yZXN0ClJhbmRvbSBmb3Jlc3QgaXMgYW4gZW5zZW1ibGUgbWFjaGluZSBsZWFybmluZyBtb2RlbC4gVGhlIFJhbmRvbSBGb3Jlc3QgbW9kZWwgaXMgYSBjb2xsZWN0aW9uIG9mIHNldmVyYWwgZGVjaXNpb24gdHJlZXMuIFRoZXNlIHRyZWVzIGNvbWUgdG9nZXRoZXIgdG8gYSBjb21iaW5lZCBkZWNpc2lvbiB0byBnaXZlIHRoZSBvdXRwdXQuIFRoZSBSYW5kb20gRm9yZXN0IG1vZGVsIHNob3VsZCBoYXZlIHRyZWVzIHdoaWNoIGFyZSB1bmNvcnJlbGF0ZWQuCgpXaGVuIGRhdGEgcG9pbnRzIGFyZSBleHRyZW1lbHkgY29tcGxpY2F0ZWQgaW4gYXJyYW5nZW1lbnQsIGEgc2ltcGxlIGNsYXNzaWZpY2F0aW9uIG1vZGVsIGxpa2UgTG9naXN0aWMgUmVncmVzc2lvbiBtYXkgdW5kZXJmaXQgdGhlIG1vZGVsIGFuZCBtYWtlIHdyb25nIHByZWRpY3Rpb25zLiBGdXJ0aGVyLCB0aGUgdGltZSB0YWtlbiBmb3IgdGhlIHN5c3RlbSB0byBwcm9jZXNzIGl0IHdvdWxkIGFsc28gYmUgY29uc2lkZXJhYmx5IGhpZ2guIFRoZXJlZm9yZSwgd2UgYXJlIGxvc2luZyBvdXQgb24gYm90aCBlbmRzLiBJbiBzdWNoIHNjZW5hcmlvLCBSYW5kb20gRm9yZXN0IGNhbiByZWFsaXplIHRoYXQgYW5kIGhhdmUgbW9yZSBhY2N1cmF0ZSBwcmVkaWN0aW9ucy4gRnVydGhlciwgb3ZlcmZpdHRpbmcgaXMgYXZvaWRlZCBhbmQgdGhlIG1vZGVsIGNhbiBhbHNvIGhhbmRsZSBtaXNzaW5nIHZhbHVlcy4gCgojIyMjIyMgIDUuMS4xIGNyb3NzLXZhbGlkYXRpb24KQWZ0ZXIgdGhlIGNyb3NzIHZhbGlkYXRpb24gcHJvY2Vzcywgd2UgZG9uJ3Qgc2VlIGFueSByZW1hcmthYmxlIGRpZmZlcmVuY2UgYmV0d2VlbiBncm91cHMuIEl0IG1heSBkdWUgdG8gdGhlIGZlYXR1cmUgb2YgdGhlIHJhbmRvbSBmb3Jlc3Qgd2hpY2ggaGF2ZSB0cmVlcyBhcmUgdW5jb3JyZWxhdGVkLiBTbyBiYXNpY2FsbHksIHdlIHVzZSB0aGUgZGF0YSBhZnRlciBjcm9zcy12YWxpZGF0aW9uIHRvIGF2b2lkIGFueSBwb3RlbnRpYWwgaXNzdWVzIGxhdGVyLgpgYGB7ciBjaG9vc2luZ3BhcmF9CmxpYnJhcnkoQVVDKQpydW4uY3YucmY8LUZBTFNFCnNvdXJjZSgiLi4vbGliL2Nyb3NzX3ZhbGlkYXJpb25fcmYuUiIpCnNvdXJjZSgiLi4vbGliL3JhbmRfZi5SIikKZmVhdHVyZV90cmFpbiA9IGFzLm1hdHJpeChkYXRfdHJhaW5bLCAtNjAwN10pCmxhYmVsX3RyYWluID0gYXMuaW50ZWdlcihkYXRfdHJhaW4kbGFiZWwpIAoKbnRyZWVzPC1jKDEwMCwyMDAsMzAwLDQwMCw1MDApCm10cnlzPC1jKDUsMTApCmlmKHJ1bi5jdi5yZil7CiAgcmVzX2N2X3JmIDwtIG1hdHJpeCgwLCBucm93ID0gbGVuZ3RoKG50cmVlcykqbGVuZ3RoKG10cnlzKSwgbmNvbCA9IDIpCiAgZm9yKGkgaW4gMTpsZW5ndGgobXRyeXMpKXsKICAgIGNhdCgibnRyZWUgPSAiLCBudHJlZXNbaV0sICJcbiIpCiAgICBmb3IoaiBpbiAxOmxlbmd0aChudHJlZXMpKXsKICAgIGNhdCgibXRyeSA9ICIsIG10cnlzW2pdLCAiXG4iKQogICAgcmVzX2N2X3JmW2ksXSA8LSBjdi5mdW5jdGlvbl9yZigKICAgICAgZmVhdHVyZXMgPSBmZWF0dXJlX3RyYWluLCBsYWJlbHMgPSBsYWJlbF90cmFpbiwgSywgbnRyZWUgPSBudHJlZXNbal0sbXRyeT1tdHJ5c1tpXSkKICB9fQogIHNhdmUocmVzX2N2X3JmLCBmaWxlPSIuLi9vdXRwdXQvcmVzX2N2X1JGLlJEYXRhIikKICAKfWVsc2V7CiAgI2xvYWQoIi4uL291dHB1dC9yZXNfY3ZfUkYuUkRhdGEiKQp9CgoKCmBgYAoKIyMjIyMjIDUuMS4yIENob29zaW5nIFJhbmRvbSBGb3Jlc3QgdHVubmluZyBwYXJhbWV0ZXIKQnkgcnVubmluZyB0aGUgbW9kZWwgd2l0aCBkaWZmZXJlbnQgcGFyYW1ldGVyIHdpdGggc2FtZSB0cmFpbmluZyBkYXRhLiBXZSBnZXQgc2V2ZXJhbCBkaWZmZXJlbnQgbnVtYmVyIG9mIGFjY3VyYWN5IGFuZCBBVUMuIFNpbmNlIHdlIGRvIG5vdCBuZWVkIHRvIGNvbnNpZGVyIG92ZXJmaXR0aW5nIGZvciB0aGUgcmFuZG9tIGZvcmVzdCBtb2RlbCBhbmQgaW4gdGhlIHNlZWsgb2YgdGltZSBmb3IgdGhlIHRyYWluaW5nIG1vZGVsIGFuZCB0ZXN0IG1vZGVsLCB3ZSBkZWNpZGVkIHRvIGNob29zZSB0aGUgcGFyYW1ldGVyIHdpdGggdGhlIGhpZ2hlc3QgYWNjdXJhY3kgYW5kIEFVQyB3aXRoIHRoZSBtaW5pbXVtIG51bWJlciBvZiB0cmVlcyBhbmQgbXRyeXMuCgpTbyB3ZSBjaG9vc2UgdGhlIHBhcmFtZXRlciwgbnRyZWVzID0gMTAwLCBtdHJ5ID0yMC4gCmBgYHtyIGZpbmQgcGFyYX0Kc291cmNlKCIuLi9saWIvcmFuZF9mLlIiKQojIHRyYWluaW5nIHJmCiMgbnRyZWU9MTAwMCwgIG5vZGVzaXplPTI1LCBzYW1wbGVzaXplPW5yb3codHJhaW54KQpydW4udHJhaW4uUkY8LUZBTFNFCmZlYXR1cmVfdHJhaW4gPSBhcy5tYXRyaXgoZGF0X3RyYWluWywgLTYwMDddKQpsYWJlbF90cmFpbiA9IGFzLmludGVnZXIoZGF0X3RyYWluJGxhYmVsKSAKbnRyZWVzPC1jKDI1LDEwMCwzMDAsNTAwLDgwMCkKbXRyeXM8LWMoMjApCnJ1bi5maW5kLlJGPC1UUlVFCmlmIChydW4uZmluZC5SRikgewogICAgIHJlc19wYV9yZiA8LSBtYXRyaXgoTkEsbnJvdz01LG5jb2w9MikKICAgICBmb3IoaSBpbiAxOmxlbmd0aChudHJlZXMpKXsKICAgICAgICAgcmFuZG9tZm9yZXN0X2ZpdCA8LSB0cmFpbl9SRihmZWF0dXJlX3RyYWluLCBsYWJlbF90cmFpbiwgbnRyZWUgPSBudHJlZXNbaV0sIG10cnkgPSBtdHJ5cykKICAgICAgICAgbGFiZWxfcHJlZCA8LSBhcy5pbnRlZ2VyKHRlc3RfUkYocmFuZG9tZm9yZXN0X2ZpdCwgZmVhdHVyZV90ZXN0KSkKICAgICAgICAgbGFiZWxfcHJlZCA8LSBpZmVsc2UobGFiZWxfcHJlZCA9PSAyLCAwLCAxKQogICAgICAgICBhY2N1X3JmID0gc3VtKGxhYmVsX3ByZWQ9PWRhdF90ZXN0JGxhYmVsKS9sZW5ndGgoZGF0X3Rlc3QkbGFiZWwpCiAgICAgICAgIHJlc19wYV9yZltpLDFdPC1hY2N1X3JmCiAgICAgICAgIHRwci5mcHIgPC0gV2VpZ2h0ZWRST0MobGFiZWxfcHJlZCwgZGF0X3Rlc3QkbGFiZWwpCiAgICAgICAgIGF1Y19yZiA8LSBXZWlnaHRlZEFVQyh0cHIuZnByKQogICAgICAgICByZXNfcGFfcmZbaSwyXTwtYXVjX3JmCiAgICAgICAgIH0KICAgICAgc2F2ZShyZXNfcGFfcmYsIGZpbGU9Ii4uL291dHB1dC9yZXNfcGFfUkYuUkRhdGEiKQp9ZWxzZXsKICAgbG9hZCgiLi4vb3V0cHV0L3Jlc19wYV9SRi5SRGF0YSIpCn0KICAgICAKCgpgYGAKdmlzdWFsaXphdGlvbiBvZiB0aGUgcGFyYW1ldGVyIHRvIGNob29zZQpgYGB7ciBsb2FkIHBhcmF9CmxvYWQoIi4uL291dHB1dC9yZXNfcGFfUkYuUkRhdGEiKQpwbG90KG50cmVlcywgcmVzX3BhX3JmWywxXSwgdHlwZSA9ICJsIix5bGltPWMoMC43NiwwLjc5NSkpCnBvaW50cyhudHJlZXMscmVzX3BhX3JmWywyXSswLjI1KQpgYGAKCiMjIyMjIyA1LjEuMyBUcmFpbiB0aGUgbW9kZWwgd2l0aCB0dW5uaW5nIHBhcmFtZXRlciBvbiBvcmlnaW5hbCBkYXRhc2V0ClRyYWluIHRoZSBtb2RlbCwgc2F2ZSB0aGUgZGF0YSB0byB0aGUgb3V0cHV0IHJEQVRBIHdpdGggdGhlIHBhcmFtZXRlciBmaW5kIGJlZm9yZS4gCmBgYHtyIHRyYWluIFJhbmRvbSBGb3Jlc3QgfQpzb3VyY2UoIi4uL2xpYi9yYW5kX2YuUiIpCiMgdHJhaW5pbmcgcmYKCnJ1bi50cmFpbi5SRjwtVFJVRQpmZWF0dXJlX3RyYWluID0gYXMubWF0cml4KGRhdF90cmFpblssIC02MDA3XSkKbGFiZWxfdHJhaW4gPSBhcy5pbnRlZ2VyKGRhdF90cmFpbiRsYWJlbCkgCgppZiAocnVuLnRyYWluLlJGKSB7CiAgdG1fcmZUcmFpbiA8LSBzeXN0ZW0udGltZSgKICAgICByYW5kb21mb3Jlc3RfZml0IDwtIHRyYWluX1JGKGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluLCBudHJlZSA9IDEwMCwgbXRyeSA9IDIwKQopCnNhdmUocmFuZG9tZm9yZXN0X2ZpdCwgZmlsZSA9ICIuLi9vdXRwdXQvdHJhaW5fcmFuZG9tZm9yZXN0LlJEYXRhIikKfQpgYGAKCgojIyMjIyMgNS4xLjQgVGVzdCB0aGUgbW9kZWwgYnkgdHJhaW4gZGF0YSBvbiBvcmlnaW5hbCBkYXRhc2V0CmBgYHtyIHRlc3R0cmFpbn0KIyB0ZXN0aW5nIHJmCnRtX3JmX3RyYWluX3Rlc3QgPSBOQQoKcnVuLnRlc3QuUkY8LVRSVUUKaWYocnVuLnRlc3QuUkYpewogIGxvYWQoZmlsZT0iLi4vb3V0cHV0L3RyYWluX3JhbmRvbWZvcmVzdC5SRGF0YSIpCiAgdG1fcmZfdHJhaW5fdGVzdCA8LSBzeXN0ZW0udGltZSgKICAgIGxhYmVsX3ByZWRfdHJhaW4gPC0gYXMuaW50ZWdlcih0ZXN0X1JGKHJhbmRvbWZvcmVzdF9maXQsIGZlYXR1cmVfdHJhaW4pKSk7IAp9CgpsYWJlbF9wcmVkX3RyYWluIDwtIGlmZWxzZShsYWJlbF9wcmVkX3RyYWluID09IDIsIDEsIDIpCmFjY3VfcmZfdHJhaW5fdGVzdCA8LSBzdW0obGFiZWxfcHJlZF90cmFpbj09bGFiZWxfdHJhaW4pL2xlbmd0aChsYWJlbF90cmFpbikKCnRwci5mcHJfdHJhaW4gPC0gV2VpZ2h0ZWRST0MobGFiZWxfcHJlZF90cmFpbiwgbGFiZWxfdHJhaW4pCmF1Y19yZl90cmFpbiA8LSBXZWlnaHRlZEFVQyh0cHIuZnByX3RyYWluKQpgYGAKCiMjIyMjIyA1LjEuNSBUZXN0IHRoZSBtb2RlbCBieSB0ZXN0IGRhdGEgb24gb3JpZ2luYWwgZGF0YXNldApDYWxjdWxhdGUgdGVzdGluZyB0aW1lIGFuZCBhY2N1cmFjeSBvbiB0ZXN0aW5nIGRhdGEKYGBge3IgdGVzdCBSYW5kb20gRm9yZXN0fQojIHRlc3RpbmcgcmYKdG1fdGVzdF9SRiA9IE5BCmZlYXR1cmVfdGVzdCA8LSBhcy5tYXRyaXgoZGF0X3Rlc3RbLCAtNjAwN10pCnJ1bi50ZXN0LlJGPC1UUlVFCmlmKHJ1bi50ZXN0LlJGKXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC90cmFpbl9yYW5kb21mb3Jlc3QuUkRhdGEiKQogIHRtX3Rlc3RfUkYgPC0gc3lzdGVtLnRpbWUoCiAgICBsYWJlbF9wcmVkIDwtIGFzLmludGVnZXIodGVzdF9SRihyYW5kb21mb3Jlc3RfZml0LCBmZWF0dXJlX3Rlc3QpKSk7IAp9CgpgYGAKCgpgYGB7ciBldmFsdWF0aW9uX3JmLCBlY2hvPUZBTFNFfQpsYWJlbF9wcmVkMSA8LSBpZmVsc2UobGFiZWxfcHJlZCA9PSAyLCAwLCAxKQphY2N1X3JmX3Rlc3RfdGVzdCA9IHN1bShsYWJlbF9wcmVkMT09ZGF0X3Rlc3QkbGFiZWwpL2xlbmd0aChkYXRfdGVzdCRsYWJlbCkKdHByLmZwcl90ZXN0IDwtIFdlaWdodGVkUk9DKGxhYmVsX3ByZWQxLCBkYXRfdGVzdCRsYWJlbCkKYXVjX3JmX3Rlc3QgPC0gV2VpZ2h0ZWRBVUModHByLmZwcl90ZXN0KQpgYGAKCiMjIyMjIyA1LjEuNiBUcmFpbiB0aGUgbW9kZWwgd2l0aCB0dW5uaW5nIHBhcmFtZXRlciBvbiByZXNhbXBsZWQgZGF0YXNldApUcmFpbiB0aGUgbW9kZWwsIHNhdmUgdGhlIGRhdGEgdG8gdGhlIG91dHB1dCByREFUQSB3aXRoIHRoZSBwYXJhbWV0ZXIgZmluZCBiZWZvcmUuIApgYGB7ciB0cmFpbiBSYW5kb20gRm9yZXN0IHJlc2FtcGxlfQpzb3VyY2UoIi4uL2xpYi9yYW5kX2YuUiIpCiMgdHJhaW5pbmcgcmYKdG1fcmZUcmFpbl9yZXNhbXBsZTwtTkEKcnVuLnRyYWluLlJGPC1GQUxTRQoKaWYgKHJ1bi50cmFpbi5SRikgewogIHRtX3JmVHJhaW5fcmVzYW1wbGUgPC0gc3lzdGVtLnRpbWUoCiAgICAgcmFuZG9tZm9yZXN0X2ZpdF9yZXNhbXBsZSA8LSB0cmFpbl9SRihmZWF0dXJlX3RyYWluX25ldywgbGFiZWxfdHJhaW5fbmV3LCBudHJlZSA9IDEwMCwgbXRyeSA9IDIwKQopCnNhdmUocmFuZG9tZm9yZXN0X2ZpdF9yZXNhbXBsZSwgZmlsZSA9ICIuLi9vdXRwdXQvdHJhaW5fcmFuZG9tZm9yZXN0X3Jlc2FtcGxlLlJEYXRhIikKfQpgYGAKCgojIyMjIyMgNS4xLjcgVGVzdCB0aGUgbW9kZWwgYnkgdHJhaW4gZGF0YSBvbiByZXNhbXBsZWQgZGF0YXNldApgYGB7ciB0ZXN0dHJhaW4gcmVzYW1wbGV9CiMgdGVzdGluZyByZgp0bV9yZl90cmFpbl90ZXN0X3Jlc2FtcGxlID0gTkEKCnJ1bi50ZXN0LlJGPC1UUlVFCmlmKHJ1bi50ZXN0LlJGKXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC90cmFpbl9yYW5kb21mb3Jlc3RfcmVzYW1wbGUuUkRhdGEiKQogIHRtX3JmX3RyYWluX3Rlc3RfcmVzYW1wbGUgPC0gc3lzdGVtLnRpbWUoCiAgICBsYWJlbF9wcmVkX3RyYWluX3Jlc2FtcGxlIDwtIGFzLmludGVnZXIodGVzdF9SRihyYW5kb21mb3Jlc3RfZml0X3Jlc2FtcGxlLCBmZWF0dXJlX3RyYWluX25ldykpKTsgCn0KCmxhYmVsX3ByZWRfdHJhaW5fcmVzYW1wbGUgPC0gaWZlbHNlKGxhYmVsX3ByZWRfdHJhaW5fcmVzYW1wbGUgPT0gMiwgMSwgMikKYWNjdV9yZl90cmFpbl90ZXN0X3Jlc2FtcGxlIDwtIHN1bShsYWJlbF9wcmVkX3RyYWluX3Jlc2FtcGxlPT1sYWJlbF90cmFpbl9uZXcpL2xlbmd0aChsYWJlbF90cmFpbl9uZXcpCgp0cHIuZnByX3RyYWluX3Jlc2FtcGxlIDwtIFdlaWdodGVkUk9DKGxhYmVsX3ByZWRfdHJhaW5fcmVzYW1wbGUsIGxhYmVsX3RyYWluX25ldykKYXVjX3JmX3RyYWluX3Jlc2FtcGxlIDwtIFdlaWdodGVkQVVDKHRwci5mcHJfdHJhaW5fcmVzYW1wbGUpCmBgYAoKIyMjIyMjIDUuMS44IFRlc3QgdGhlIHJlc2FtcGxlZCBtb2RlbCBieSB0ZXN0IGRhdGEgb24gdGVzdCBkYXRhc2V0CkNhbGN1bGF0ZSB0ZXN0aW5nIHRpbWUgYW5kIGFjY3VyYWN5IG9uIHRlc3RpbmcgZGF0YQpgYGB7ciB0ZXN0IFJhbmRvbSBGb3Jlc3QgcmVzYW1wbGV9CiMgdGVzdGluZyByZgp0bV90ZXN0X1JGX3Jlc2FtcGxlID0gTkEKZmVhdHVyZV90ZXN0IDwtIGFzLm1hdHJpeChkYXRfdGVzdFssIC02MDA3XSkKcnVuLnRlc3QuUkY8LVRSVUUKaWYocnVuLnRlc3QuUkYpewogIGxvYWQoZmlsZT0iLi4vb3V0cHV0L3RyYWluX3JhbmRvbWZvcmVzdC5SRGF0YSIpCiAgdG1fdGVzdF9SRl9yZXNhbXBsZSA8LSBzeXN0ZW0udGltZSgKICAgIGxhYmVsX3ByZWRfcmVzYW1wbGUgPC0gYXMuaW50ZWdlcih0ZXN0X1JGKHJhbmRvbWZvcmVzdF9maXRfcmVzYW1wbGUsIGZlYXR1cmVfdGVzdCkpKTsgCn0KCmBgYAoKCmBgYHtyIGV2YWx1YXRpb25fcmZyZXNhbXBsZSwgZWNobz1GQUxTRX0KbGFiZWxfcHJlZDFfcmVzYW1wbGUgPC0gaWZlbHNlKGxhYmVsX3ByZWRfcmVzYW1wbGUgPT0gMiwgMCwgMSkKYWNjdV9yZl90ZXN0X3Rlc3RfcmVzYW1wbGUgPSBzdW0obGFiZWxfcHJlZDFfcmVzYW1wbGU9PWRhdF90ZXN0JGxhYmVsKS9sZW5ndGgoZGF0X3Rlc3QkbGFiZWwpCnRwci5mcHJfdGVzdF9yZXNhbXBsZSA8LSBXZWlnaHRlZFJPQyhsYWJlbF9wcmVkMV9yZXNhbXBsZSwgZGF0X3Rlc3QkbGFiZWwpCmF1Y19yZl90ZXN0X3Jlc2FtcGxlIDwtIFdlaWdodGVkQVVDKHRwci5mcHJfdGVzdF9yZXNhbXBsZSkKYGBgCgoKCiMjIyMjIyA1LjEuOSBTdW1tYXJ5IG9mIHRoZSByYW5kb20gZm9yZXN0IG1vZGVsCmBgYHtyIHN1bW1hcnkgcmZ9CgpjYXQoIlRoZSB1bndlaWdodGVkIGFjY3VyYWN5IG9mIHRoZSByYW5kb20gZm9yZXN0IG1vZGVsIG9uIHRyYWluIGRhdGEgaXMgIiwgYWNjdV9yZl90cmFpbl90ZXN0KjEwMCwgIiUuXG4iKQpjYXQoIlRoZSB1bndlaWdodGVkIEFVQyBvZiB0aGUgcmFuZG9tIGZvcmVzdCBtb2RlbCBvbiB0cmFpbiBkYXRhIGlzICIsIGF1Y19yZl90cmFpbiwgIi5cbiIpCmNhdCgiVGhlIHVud2VpZ2h0ZWQgYWNjdXJhY3kgb2YgdGhlIHJhbmRvbSBmb3Jlc3QgbW9kZWwgb24gdGVzdCBkYXRhIGlzICIsIGFjY3VfcmZfdGVzdF90ZXN0KjEwMCwgIiUuXG4iKQpjYXQoIlRoZSB1bndlaWdodGVkIEFVQyBvZiB0aGUgcmFuZG9tIGZvcmVzdCBtb2RlbCBvbiB0ZXN0IGRhdGEgaXMgIiwgYXVjX3JmX3Rlc3QsICIuXG4iKQpjYXQoIlRoZSByZXNhbXBsZWQgYWNjdXJhY3kgb2YgdGhlIHJhbmRvbSBmb3Jlc3QgbW9kZWwgb24gdHJhaW4gZGF0YSBpcyAiLCBhY2N1X3JmX3RyYWluX3Rlc3RfcmVzYW1wbGUqMTAwLCAiJS5cbiIpCmNhdCgiVGhlIHJlc2FtcGxlZCBBVUMgb2YgdGhlIHJhbmRvbSBmb3Jlc3QgbW9kZWwgb24gdHJhaW4gZGF0YSBpcyAiLCBhdWNfcmZfdHJhaW5fcmVzYW1wbGUsICIuXG4iKQpjYXQoIlRoZSByZXNhbXBsZWQgYWNjdXJhY3kgb2YgdGhlIHJhbmRvbSBmb3Jlc3QgbW9kZWwgb24gdGVzdCBkYXRhIGlzICIsIGFjY3VfcmZfdGVzdF90ZXN0X3Jlc2FtcGxlKjEwMCwgIiUuXG4iKQpjYXQoIlRoZSByZXNhbXBsZWQgQVVDIG9mIHRoZSByYW5kb20gZm9yZXN0IG1vZGVsIG9uIHRlc3QgZGF0YSBpcyAiLCBhdWNfcmZfdGVzdF9yZXNhbXBsZSwgIi5cbiIpCgpjYXQoIlRpbWUgZm9yIHRyYWluaW5nIG1vZGVsIFJhbmRvbSBGb3Jlc3QgPSAiLCB0bV9yZlRyYWluWzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgdGVzdCBtb2RlbCBSYW5kb20gRm9yZXN0IG9uIHRyYWluIGRhdGEgaXMgIix0bV9yZl90cmFpbl90ZXN0WzFdICwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIHRlc3QgbW9kZWwgUmFuZG9tIEZvcmVzdCBvbiB0ZXN0IGRhdGEgaXMgIix0bV90ZXN0X1JGWzFdICwgInMgXG4iKQpgYGAKCgoKCgojIyMjI1RoZSB0ZXN0IGRheSBjb2RlCmBgYHtyIHRlc3QgZGF5fQojIHRlc3RpbmcgcmYKdG1fdGVzdF9SRl90ZCA9IE5BCmZlYXR1cmVfdGVzdF90ZCA8LSBhcy5tYXRyaXgodGRbLCAtNjAwN10pCnJ1bi50ZXN0LlJGPC1UUlVFCmlmKHJ1bi50ZXN0LlJGKXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC90cmFpbl9yYW5kb21mb3Jlc3QuUkRhdGEiKQogIHRtX3Rlc3RfUkZfdGQgPC0gc3lzdGVtLnRpbWUoCiAgICBsYWJlbF9wcmVkX3RkIDwtIGFzLmludGVnZXIodGVzdF9SRihyYW5kb21mb3Jlc3RfZml0X3Jlc2FtcGxlLCBmZWF0dXJlX3Rlc3RfdGQpKSkKICAgd3JpdGUuY3N2KGxhYmVsX3ByZWRfdGQsICJteWRhdGEuY3N2IikKfQoKYGBgCgoKCgoKCgoKCgoKCgoKIyMjUmVmZXJlbmNlCi0gRHUsIFMuLCBUYW8sIFkuLCAmIE1hcnRpbmV6LCBBLiBNLiAoMjAxNCkuIENvbXBvdW5kIGZhY2lhbCBleHByZXNzaW9ucyBvZiBlbW90aW9uLiBQcm9jZWVkaW5ncyBvZiB0aGUgTmF0aW9uYWwgQWNhZGVteSBvZiBTY2llbmNlcywgMTExKDE1KSwgRTE0NTQtRTE0NjIuCgoKCgoKCgoKCgoKCgo=